﻿#region 信息
// Copyright©2014 Rex Rao all rights reserved.
// File name : Mojito\Mojito.Service\OrderService.cs
// Author : Sohobloo
// Create time : 2014/09/03 17:15
// Update time : 2014/09/03 17:15
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using Mojito.Common;
using Mojito.Data.Repository;
using Mojito.Entity;
using Sohobloo.SoCommon;
using Sohobloo.SoCommon.CommonExtension;
using Sohobloo.SoMcCoffee.Service;

namespace Mojito.Service.Impl
{
    [Export(typeof(IOrderService))]
    internal class OrderService : ServiceBase, IOrderService
    {
        [Import]
        public IOrderRepository OrderRepository { get; set; }

        [Import]
        public ITableRepository TableRepository { get; set; }

        [Import]
        public IAccountItemRepository AccountItemRepository { get; set; }

        [Import]
        public IOrderDishRepository OrderDishRepository { get; set; }

        [Import]
        public IShopConfig ShopConfig { get; set; }

        [Import]
        public IDishRepository DishRepository { get; set; }

        public Order GetOrder(Guid orderId)
        {
            return OrderRepository.GetByKey(orderId);
        }

        public IList<Order> GetCurrentOrderList()
        {
            var periodDate = GetPeriodDate(DateTime.UtcNow);
            var currentPeriodQuery = OrderRepository.Entities
                                                    .Where(o => o.PeriodDate == periodDate);
            var notEnterAccountQuery = OrderRepository.Entities
                                                      .Where(o => o.OrderStatus != OrderStatus.AccountEntered);
            return currentPeriodQuery.Union(notEnterAccountQuery)
                                     .OrderBy(o => o.OrderTime)
                                     .ToList();
        }

        public OperationResult CreateOrder()
        {
            var periodDate = GetPeriodDate(DateTime.UtcNow);
            var orderNumber = GenerateOrderNumber(periodDate);
            var obfuscatedOrderNumber = OrderNumberObfuscator.Obfuscate(orderNumber);
            var order = new Order
            {
                OrderNumber = orderNumber,
                ObfuscatedOrderNumber = obfuscatedOrderNumber,
                OrderTime = DateTime.UtcNow,
                PeriodDate = periodDate,
                GuestNumber = 1,
                OrderStatus = OrderStatus.NotServing
            };
            OrderRepository.Insert(order);
            var result = Commit();
            result.AppendData = order;
            return result;
        }

        public OperationResult UpdateOrder(OrderUpdateParams parameters)
        {
            ArgumentHelper.Instance
                          .CheckNull(parameters)
                          .CheckRange(parameters.GuestNumber, 1, 99)
                          .CheckRange(parameters.AccountReceived, 0, 999999.99);
            var order = OrderRepository.GetByKey(parameters.OrderId);
            if(order.IsNull())
            {
                return new OperationResult(OperationResultType.NotExist, "Order not exist.");
            }

            var needRecalcMinConsumptionAmount = parameters.TableId != order.TableId
                                                 || parameters.GuestNumber != order.GuestNumber;
            order.PeriodDate = parameters.PeriodDate;
            order.TableId = parameters.TableId;
            order.GuestNumber = parameters.GuestNumber;
            order.AccountRecieved = parameters.AccountReceived;
            order.Notes = parameters.Notes;
            if (needRecalcMinConsumptionAmount)
            {
                if(order.TableId.HasValue)
                {
                    var table = TableRepository.GetByKey(order.TableId.Value);
                    if(table.IsNull())
                    {
                        return new OperationResult(OperationResultType.NotExist, "Table not exist.");
                    }
                    switch(table.MinConsumptionType)
                    {
                        case MinConsumptionType.None:
                            order.MinConsumptionAmount = 0;
                            break;
                        case MinConsumptionType.PerCapital:
                            order.MinConsumptionAmount = table.MinConsumptionAmount * order.GuestNumber;
                            break;
                        case MinConsumptionType.PerTable:
                            order.MinConsumptionAmount = table.MinConsumptionAmount;
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                }
                else
                {
                    order.MinConsumptionAmount = 0;
                }
            }

            OrderRepository.Update(order);
            var result = Commit();
            result.AppendData = order;
            return result;
        }

        public OperationResult DeleteOrder(Guid orderId)
        {
            var order = OrderRepository.GetByKey(orderId);
            if(order.IsNull())
            {
                return new OperationResult(OperationResultType.NotExist, "Order not exist.");
            }

            // 解除AccountItem关联
            var accountItem = AccountItemRepository.Entities.SingleOrDefault(ai => ai.OrderId == orderId);
            if(accountItem != null)
            {
                accountItem.OrderId = null;
                AccountItemRepository.Update(accountItem);
            }

            // 删除OrderDishes
            OrderDishRepository.Delete(order.Dishes);

            OrderRepository.Delete(order);
            var result = Commit();
            result.AppendData = order;
            return result;
        }

        public OperationResult AddOrderDishes(Guid orderId, IList<Guid> dishIds)
        {
            ArgumentHelper.Instance.CheckNull(dishIds);
            var order = OrderRepository.GetByKey(orderId);
            if (order.IsNull())
            {
                return new OperationResult(OperationResultType.NotExist, "Order not exist.");
            }
            foreach(var dishId in dishIds)
            {
                var dish = DishRepository.GetByKey(dishId);
                if(dish.IsNull())
                {
                    return new OperationResult(OperationResultType.NotExist, "Dish not exist.");
                }
                var orderDish = new OrderDish
                {
                    OrderId = orderId,
                    DishId = dishId,
                    Price = dish.Price,
                    OrderDishesStatus = OrderDishStatus.NotServing
                };
                OrderDishRepository.Insert(orderDish);
            }
            order.AccountReceivable = CalculateAccountReceivable(orderId);
            order.OrderStatus = EvaluateOrderStatus(orderId);
            OrderRepository.Update(order);

            var result = Commit();
            result.AppendData = order;
            return result;
        }

        public OperationResult UpdateOrderDish(Guid orderDishId, OrderDishStatus orderDishStatus)
        {
            var orderDish = OrderDishRepository.GetByKey(orderDishId);
            if(orderDishId.IsNull())
            {
                return new OperationResult(OperationResultType.NotExist, "Order dish not exist.");
            }
            orderDish.OrderDishesStatus = orderDishStatus;
            OrderDishRepository.Update(orderDish);
            var order = orderDish.Order;
            order.OrderStatus = EvaluateOrderStatus(order.Id);
            OrderRepository.Update(order);

            var result = Commit();
            result.AppendData = order;
            return result;
        }

        public OperationResult DeleteOrderDishes(IList<Guid> orderDishIds)
        {
            var order = OrderDishRepository.Entities
                                           .Where(od => orderDishIds.Contains(od.Id))
                                           .Select(od => od.Order)
                                           .Distinct()
                                           .Single();
            foreach(var orderDishId in orderDishIds)
            {
                OrderDishRepository.Delete(orderDishId);
            }
            order.AccountReceivable = CalculateAccountReceivable(order.Id);
            order.OrderStatus = EvaluateOrderStatus(order.Id);
            OrderRepository.Update(order);

            var result = Commit();
            result.AppendData = order;
            return result;
        }

        public OperationResult EnterAccount(Guid orderId)
        {
            throw new NotImplementedException();
        }

        private DateTime GetPeriodDate(DateTime time)
        {
            var timeZone = ShopConfig.TimeZone;
            var dailySettlementTime = ShopConfig.DailySettlementTime;
            var localTime = TimeZoneInfo.ConvertTimeFromUtc(time, timeZone);
            return localTime.Add(-dailySettlementTime).Date;
        }

        private string GenerateOrderNumber(DateTime periodDate)
        {
            var currentOrderNumber = OrderRepository.Entities
                .Where(o => o.PeriodDate == periodDate)
                .Max(o => o.OrderNumber);
            var currentSerialNumber = currentOrderNumber.IsNotNullOrEmpty()
                ? Convert.ToInt32(currentOrderNumber.Substring(8))
                : 0;
            return periodDate.ToString("yyyyMMdd") + (currentSerialNumber + 1).ToString("D4");
        }

        private double CalculateAccountReceivable(Guid orderId)
        {
            return OrderDishRepository.Entities
                                      .Where(od => od.OrderId == orderId)
                                      .Sum(od => od.Price);
        }

        private OrderStatus EvaluateOrderStatus(Guid orderId)
        {
            var order = OrderRepository.GetByKey(orderId);
            ArgumentHelper.Instance.CheckNull(order);
            if (order.OrderStatus == OrderStatus.AccountEntered 
                || order.OrderStatus == OrderStatus.Paid)
            {
                return order.OrderStatus;
            }
            if(order.Dishes.Any() && order.Dishes.All(od => od.OrderDishesStatus == OrderDishStatus.Served))
            {
                return OrderStatus.Unpaid;
            }
            return OrderStatus.NotServing;
        }
    }
}